package validate

import (
	"fmt"
	"reflect"
	"strings"
	"strconv"
	"encoding/json"
	ex "regexp"
)

type Handler func(value interface{}, param string) error

type validate struct {
	jsonName 			string
	validateName 		string
	handlers			map[string]Handler
}

func JSONValidate() (*validate) {
	return &validate{
		jsonName: 		"json",
		validateName: 	"validate",
		handlers: 		map[string]Handler{
			"required": required,
			"length": 	length,
			"regexp": 	regexp,
			"empty": 	empty,
			"enum": 	enum,
		},
	}
}

func (v *validate) SetHandler(name string, handler Handler) (*validate) {
	v.handlers[name] = handler
	return v
}

func (v *validate) ParseMap(data map[string]interface{}, i interface{}) error {
	return v.execute(data, i)
}

func (v *validate) ParseJSONByte(body []byte, i interface{}) error {
	data := make(map[string]interface{})
	if err := json.Unmarshal(body, &data); err != nil {
		return fmt.Errorf("json unmarshal error: %v", err)
	}
	return v.execute(data, i)
}

func (v *validate) ParseJSONString(body string, i interface{}) error {
	data := make(map[string]interface{})
	if err := json.Unmarshal([]byte(body), &data); err != nil {
		return fmt.Errorf("json unmarshal error: %v", err)
	}
	return v.execute(data, i)
}

func (v *validate) execute(data map[string]interface{}, i interface{}) error {
	it := reflect.TypeOf(i).Elem()
	iv := reflect.ValueOf(i).Elem()

	for i := 0; i < it.NumField(); i++ {
		field := it.Field(i)
		name  := field.Tag.Get(v.jsonName)

		if len(name) <= 0 {
			name = field.Name
		}

		value, ok := data[name]

		if err := v.validate(field, value); err != nil {
			return fmt.Errorf("validator [%s]: %v", name, err)
		}

		if !ok {
			continue
		}

		vField := iv.Field(i)
		if err := v.set(vField, field, value); err != nil {
			return fmt.Errorf("value [%s]: %v", name, err)
		}
	}

	return nil
}

func (v *validate) validate(field reflect.StructField, value interface{}) error {
	tags := v.tags(field.Tag.Get(v.validateName))
	for k, param := range tags {
		if handler, ok := v.handlers[k]; ok {
			if err := handler(value, param); err != nil {
				return fmt.Errorf("[%s]: %s", k, err)
			}
		}
	}
	return nil
}

func (v *validate) tags(tag string) (map[string]string) {
	texts := strings.Split(tag, ",")
	tags  := make(map[string]string)
	for i := 0; i < len(texts); i++ {
		ts := strings.SplitN(texts[i], "=", 2)
		n  := strings.Trim(ts[0], " ")
		v  := ""
		if len(ts) > 1 {
			v = strings.Trim(ts[1], " ")
		}
		if len(n) > 0 {
			tags[n] = v
		}
	}
	return tags
}

func (v *validate) set(vField reflect.Value, field reflect.StructField, value interface{}) error {
	switch field.Type.Kind() {
	case reflect.String:
		switch reflect.TypeOf(value).Kind() {
		case reflect.String:
			vField.SetString(value.(string))
		case reflect.Slice, reflect.Map:
			sv, err := json.Marshal(value)
			if err != nil {
				return err
			}
			vField.SetString(string(sv))
		default:
			vv, ok := value.(float64)
			if !ok {
				return fmt.Errorf("value %s to float64 error", reflect.TypeOf(value).Kind())
			}
			vField.SetString(strconv.Itoa(int(vv)))
		}
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		if reflect.TypeOf(value).Name() == reflect.String.String() {
			vv, err := strconv.Atoi(value.(string))
			if err != nil {
				return err
			}
			vField.SetInt(int64(vv))
		} else {
			vv, ok := value.(float64)
			if !ok {
				return fmt.Errorf("value %s to float64 error", reflect.TypeOf(value).Kind())
			}
			vField.SetInt(int64(vv))
		}
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		if reflect.TypeOf(value).Name() == reflect.String.String() {
			vv, err := strconv.Atoi(value.(string))
			if err != nil {
				return err
			}
			vField.SetUint(uint64(vv))
		} else {
			vv, ok := value.(float64)
			if !ok {
				return fmt.Errorf("value %s to float64 error", reflect.TypeOf(value).Kind())
			}
			vField.SetUint(uint64(vv))
		}
	case reflect.Float32, reflect.Float64:
		if reflect.TypeOf(value).Name() == reflect.String.String() {
			vv, err := strconv.Atoi(value.(string))
			if err != nil {
				return err
			}
			vField.SetFloat(float64(vv))
		} else {
			vv, ok := value.(float64)
			if !ok {
				return fmt.Errorf("value %s to float64 error", reflect.TypeOf(value).Kind())
			}
			vField.SetFloat(vv)
		}
	case reflect.Struct:
		st := reflect.New(field.Type)
		v.execute(value.(map[string]interface{}), st.Interface())
		vField.Set(st.Elem())
	case reflect.Ptr:
		st := reflect.New(field.Type.Elem())
		v.execute(value.(map[string]interface{}), st.Interface())
		vField.Set(st)
	case reflect.Slice:
		var val []byte
		var err error

		if reflect.TypeOf(value).Name() != reflect.String.String() {
			if reflect.TypeOf(value).Kind() != reflect.Slice {
				return fmt.Errorf("value is not string or slice")
			}
			if val, err = json.Marshal(value); err != nil {
				return err
			}
		} else {
			val = []byte(value.(string))
		}

		switch field.Type.Elem().Kind() {
		case reflect.String:
			var vv []string
			if err := json.Unmarshal(val, &vv); err != nil {
				return err
			}
			vField.Set(reflect.ValueOf(vv))
		case reflect.Int:
			var vv []int
			if err := json.Unmarshal(val, &vv); err != nil {
				return err
			}
			vField.Set(reflect.ValueOf(vv))
		case reflect.Int8:
			var vv []int8
			if err := json.Unmarshal(val, &vv); err != nil {
				return err
			}
			vField.Set(reflect.ValueOf(vv))
		case reflect.Int32:
			var vv []int32
			if err := json.Unmarshal(val, &vv); err != nil {
				return err
			}
			vField.Set(reflect.ValueOf(vv))
		case reflect.Int64:
			var vv []int64
			if err := json.Unmarshal(val, &vv); err != nil {
				return err
			}
			vField.Set(reflect.ValueOf(vv))
		case reflect.Uint:
			var vv []uint
			if err := json.Unmarshal(val, &vv); err != nil {
				return err
			}
			vField.Set(reflect.ValueOf(vv))
		case reflect.Uint8:
			var vv []uint8
			if err := json.Unmarshal(val, &vv); err != nil {
				return err
			}
			vField.Set(reflect.ValueOf(vv))
		case reflect.Uint32:
			var vv []uint32
			if err := json.Unmarshal(val, &vv); err != nil {
				return err
			}
			vField.Set(reflect.ValueOf(vv))
		case reflect.Uint64:
			var vv []uint64
			if err := json.Unmarshal(val, &vv); err != nil {
				return err
			}
			vField.Set(reflect.ValueOf(vv))
		case reflect.Float32:
			var vv []float32
			if err := json.Unmarshal(val, &vv); err != nil {
				return err
			}
			vField.Set(reflect.ValueOf(vv))
		case reflect.Float64:
			var vv []float64
			if err := json.Unmarshal(val, &vv); err != nil {
				return err
			}
			vField.Set(reflect.ValueOf(vv))
		case reflect.Struct:
			svs := make([]interface{}, 0)
			if err := json.Unmarshal(val, &svs); err != nil {
				return err
			}
			slice := reflect.MakeSlice(field.Type, 0, 0)
			for _, sv := range svs {
				st := reflect.New(field.Type.Elem())
				v.execute(sv.(map[string]interface{}), st.Interface())
				slice = reflect.Append(slice, st.Elem())
			}
			vField.Set(slice)
		case reflect.Ptr:
			svs := make([]interface{}, 0)
			if err := json.Unmarshal(val, &svs); err != nil {
				return err
			}
			slice := reflect.MakeSlice(field.Type, 0, 0)
			for _, sv := range svs {
				st := reflect.New(field.Type.Elem().Elem())
				v.execute(sv.(map[string]interface{}), st.Interface())
				slice = reflect.Append(slice, st)
			}
			vField.Set(slice)
		default:
			return fmt.Errorf("unknown kind: %s", field.Type.Elem().Kind().String())
		}
	default:
		return fmt.Errorf("unknown kind: %s", field.Type.Kind().String())
	}
	return nil
}

func required(value interface{}, param string) error {
	if param != "false" && value == nil {
		return fmt.Errorf("then is required")
	}
	return nil
}

func length(value interface{}, param string) error {
	if value == nil {
		return nil
	}
	v, ok := value.(string)
	if !ok {
		return fmt.Errorf("value to string error")
	}
	l, _ := strconv.Atoi(param)
	if len(v) != l {
		return fmt.Errorf("value length not eq %d", l)
	}
	return nil
}

func regexp(value interface{}, param string) error {
	if value == nil {
		return nil
	}
	v, ok := value.(string)
	if !ok {
		return fmt.Errorf("value to string error")
	}
	re, err := ex.Compile(param)
	if err != nil {
		return fmt.Errorf("value regexp compile error: %s", err)
	}
	if !re.MatchString(v) {
		return fmt.Errorf("value regexp mismatch")
	}
	return nil
}

func empty(value interface{}, param string) error {
	if param == "true" || value == nil{
		return nil
	}
	v, ok := value.(string)
	if !ok {
		return fmt.Errorf("value to string error")
	}
	if len(v) <= 0 {
		return fmt.Errorf("value is empty")
	}
	return nil
}

func enum(value interface{}, param string) error {
	if value == nil {
		return nil
	}
	params := strings.Split(param, ",")
	v, ok := value.(string)
	if !ok {
		return fmt.Errorf("value to string error")
	}
	for _, p := range params {
		if p == v {
			return nil
		}
	}
	return fmt.Errorf("value not in enum[%s]", param)
}